package com.mycase.im.boot.websocket;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mycase.im.boot.entity.communication.ClientMessage;
import com.mycase.im.boot.entity.database.*;
import com.mycase.im.boot.exception.CustomException;
import com.mycase.im.boot.service.*;
import com.mycase.im.boot.entity.communication.ResponseMessage;
import com.mycase.im.boot.utils.SpringIocHolder;
import com.mycase.im.boot.websocket.pattern.MessageStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import javax.swing.*;
import javax.validation.constraints.NotNull;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * WebSocket服务端点, 即时聊天核心处理类
 * 在这里需要注意几点
 *      1. 虽然使用了@Component, 但是不能直接获取SpringIoc容器中的东西, 只能通过工具类(容器类)来获取
 *      2. 使用`@Service`层中的逻辑, `@Transitional`依然可以回滚操作,
 *              但是如果抛出异常, `@ControllerAdvice`是没办法进行异常获取的,
 *              那么就需要自己在外层进行异常的捕获(#try catch), 然后使用WebSocket返回错误消息,
 *              否则不抓取异常时会导致当前连接断开
 *      3. 使用构造器获取的不能用到`@ServerEndpoint`注解中, 虽然可以获取容器中的数据,
 *              但是可能因为执行的时机不同, 导致使用失败, 会导致空指针异常, 只能使用`static`静态代码块
 * @Author ZhuHJay
 * @Date 2021/8/2 10:50
 */
@ServerEndpoint("/ws/{userId}")
@Component
@Slf4j
public class WebSocketServerEndpoint {

    /** 存储当前在线的所有用户会话信息 **/
    private static Map<String, Session> clientMaps = new ConcurrentHashMap<>();

    /** 定义 userId 的路径变量名 **/
    private static final String USER_ID_PATH_PARAM = "userId";

    /** 实例化一个 ObjectMapper 对象 **/
    private static ObjectMapper objectMapper = new ObjectMapper();

    /** 当前类中的会话信息 **/
    private Session session;

    /** 所有的策略器: 进行消息处理的策略器 **/
    private static List<MessageStrategy> strategies = new ArrayList<>();

    static {
        // 设置序列化对象要求字段不为空
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 获取所有的策略器
        Map<String, MessageStrategy> strategyMap = SpringIocHolder.getBeansOfType(MessageStrategy.class);
        strategyMap.forEach((key, strategy) -> strategies.add(strategy));
    }

    /** 连接成功 **/
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        String userId = session.getPathParameters().get(USER_ID_PATH_PARAM);
        // 判断, 如果该用户id在map中存在了, 那么就是将旧的挤下线
        if( clientMaps.containsKey(userId) ){
            log.warn("用户{}已经存在, 即将被迫挤下线", userId);
            Session oldSession = clientMaps.get(userId);
            try {
                // 关闭旧的会话
                oldSession.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        clientMaps.put(userId, session);
        log.warn("用户{}已经放到容器中去了", session.getPathParameters().get("userId"));
    }

    /** 连接关闭 **/
    @OnClose
    public void onClose(Session session) {
        String userId = session.getPathParameters().get(USER_ID_PATH_PARAM);
        this.session = null;
        // 判断集合中有才能进行删除
        if( clientMaps.containsKey(userId) ){
            clientMaps.remove(userId);
        }
        log.warn("用户{}连接关闭", userId);
    }

    /** 接收到消息 **/
    @OnMessage
    public void onMessage(String message) throws Exception {
        console();
        // 解析 message 对象
        ClientMessage clientMessage = objectMapper.readValue(message, ClientMessage.class);
        log.warn("服务器获取到的消息数据:{}", clientMessage);

        try{
            // 遍历所有的策略, 找到合适的策略进行方法的调用
            for (int i = 0, count = strategies.size(); i < count; i++) {
                // 进行类型的校验, 判断是否可以进行使用
                if( strategies.get(i).isUse(clientMessage.getType()) ){
                    strategies.get(i).executeHandler(clientMessage, objectMapper, clientMaps, session);
                    break;
                }
                // 判断是否没有执行的策略器
                if( i == count - 1 ){
                    throw new CustomException("没有找到合适的策略器 -> type=" + clientMessage.getType());
                }
            }
        }catch (CustomException e){
            log.warn("WebSocket执行异常CustomException -> mes:{}", e.getMessage());
            sendMessage(ResponseMessage.error(e.getMessage()));
        }
    }

    /** 发送给本人的 **/
    private void sendMessage(ResponseMessage message) throws Exception{
        session.getBasicRemote().sendText(
                objectMapper.writeValueAsString(message)
        );
    }

    /** 获取在线人数地方法 **/
    private static Integer getOnLineCount(){
        return clientMaps.size();
    }

    /** 用来测试地方法 **/
    private void console(){
        System.out.println("^^^^^^^^^^^^ClientMap^^^^^^^^^^^^^");
        System.out.println("当前在线人数 -> " + getOnLineCount());
        clientMaps.forEach((k,v)->
                System.out.println("id:" + k + ", session:" + v));
        System.out.println("============ClientMap=============");
    }

}
